/* w_piem.c:  Pie Menus
 */

/*
 *
 * Pie Menus for Tk.
 * Copyright (C) 1992 by Don Hopkins.
 *
 * This program is provided for unrestricted use, provided that this 
 * copyright message is preserved. There is no warranty, and no author 
 * or distributer accepts responsibility for any damage caused by this 
 * program. 
 * 
 * This code and the ideas behind it were developed over time by Don Hopkins 
 * with the support of the University of Maryland, UniPress Software, Sun
 * Microsystems, DUX Software, the Turing Institute, and Carnegie Mellon
 * University.  Pie menus are NOT patented or restricted, and the interface 
 * and algorithms may be freely copied and improved upon. 
 *
 */

#if 0
/* workaround to make gcc work on suns */
#    ifndef SOLARIS2
#        define _PTRDIFF_T
#        define _SIZE_T
#        ifndef sgi
typedef unsigned int size_t;
#        endif
#    endif
#endif

#include "tkconfig.h"
#include "default.h"
#include "tkint.h"
#include <X11/extensions/shape.h>

#define PI 3.1415926535897932
#define TWO_PI 6.2831853071795865
#define DEG_TO_RAD(d) (((d) * TWO_PI) / 360.0)
#define RAD_TO_DEG(d) (((d) * 360.0) / TWO_PI)
#define PIE_SPOKE_INSET 6
#define PIE_BG_COLOR "#bfbfbf"
#define PIE_BG_MONO WHITE
#define PIE_ACTIVE_FG_COLOR BLACK
#define PIE_ACTIVE_FG_MONO BLACK
#define PIE_ACTIVE_BG_COLOR "#bfbfbf"
#define PIE_ACTIVE_BG_MONO WHITE
#define PIE_FG BLACK
#define PIE_FONT "-Adobe-Helvetica-Bold-R-Normal-*-120-*"
#define PIE_ACTIVE_BORDER_WIDTH "2"
#define PIE_INACTIVE_RADIUS "8"
#define PIE_INACTIVE_RADIUS_NUM 8
#define PIE_MIN_RADIUS "16"
#define PIE_MIN_RADIUS_NUM 16
#define PIE_EXTRA_RADIUS "2"
#define PIE_EXTRA_RADIUS_NUM 2
#define PIE_BORDER_WIDTH "2"
#define PIE_POPUP_DELAY "250"
#define PIE_POPUP_DELAY_NUM 250
#define PIE_ENTRY_ACTIVE_BG ((char *) NULL)
#define PIE_ENTRY_BG ((char *) NULL)
#define PIE_ENTRY_FONT ((char *) NULL)

#ifndef MAX
#    define MAX(x,y) ((x)>(y)?(x):(y))
#    define MIN(x,y) ((x)<(y)?(x):(y))
#endif
#define ABS(x)	 (((x)<0)?(-(x)):(x))

static int HaveShape = -1;

/*
 * One of the following data structures is kept for each entry of each
 * pie menu managed by this file:
 */

typedef struct PieMenuEntry
{
    int type;
    struct PieMenu *piemenuPtr;
    char *label;
    int labelLength;
    Pixmap bitmap;

    /*
     * Information related to displaying entry:
     */

    int width, height;
    int x, y;
    int x_offset, y_offset;
    int label_x, label_y;

    Tk_3DBorder border;
    Tk_3DBorder activeBorder;
    XFontStruct *fontPtr;
    GC textGC;
    GC activeGC;

    /*
     * Information used for pie menu layout & tracking:
     */

    int slice;                  /* Desired relative slice size */
    float angle;                /* Angle through center of slice */
    float dx, dy;               /* Cosine and sine of angle */
    float subtend;              /* Angle subtended by slice */
    int quadrant;               /* Quadrant of leading edge */
    float slope;                /* Slope of leading edge */

    /*
     * Information used to implement this entry's action:
     */

    char *command;
    char *preview;
    char *name;

    /*
     * Miscellaneous information:
     */

    int flags;                  /* Various flags. See below for definitions. */
} PieMenuEntry;

/*
 * Flag values defined for menu entries:
 *
 * ENTRY_NEEDS_REDISPLAY:	Non-zero means the entry should be redisplayed.
 */

#define ENTRY_NEEDS_REDISPLAY	1

/*
 * Types defined for PieMenuEntries:
 */

#define COMMAND_ENTRY		0
#define PIEMENU_ENTRY		1

/*
 * Mask bits for above types:
 */

#define COMMAND_MASK		TK_CONFIG_USER_BIT
#define PIEMENU_MASK		(TK_CONFIG_USER_BIT << 1)
#define ALL_MASK		(COMMAND_MASK | PIEMENU_MASK)

/*
 * Configuration specs for individual menu entries:
 */

static Tk_ConfigSpec entryConfigSpecs[] = {
    {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
     PIE_ENTRY_ACTIVE_BG, Tk_Offset(PieMenuEntry, activeBorder),
     ALL_MASK | TK_CONFIG_NULL_OK},
    {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
     PIE_ENTRY_BG, Tk_Offset(PieMenuEntry, border),
     ALL_MASK | TK_CONFIG_NULL_OK},
    {TK_CONFIG_PIXMAP, "-bitmap", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(PieMenuEntry, bitmap),
     ALL_MASK | TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(PieMenuEntry, command),
     COMMAND_MASK},
    {TK_CONFIG_STRING, "-preview", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(PieMenuEntry, preview),
     ALL_MASK},
    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
     PIE_ENTRY_FONT, Tk_Offset(PieMenuEntry, fontPtr),
     ALL_MASK | TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(PieMenuEntry, label),
     ALL_MASK},
    {TK_CONFIG_STRING, "-piemenu", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(PieMenuEntry, name),
     ALL_MASK},
    {TK_CONFIG_INT, "-xoffset", "xOffset", "XOffset",
     "0", Tk_Offset(PieMenuEntry, x_offset),
     ALL_MASK},
    {TK_CONFIG_INT, "-yoffset", "yOffset", "YOffset",
     "0", Tk_Offset(PieMenuEntry, y_offset),
     ALL_MASK},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, 0}
};

/*
 * A data structure of the following type is kept for each
 * pie menu managed by this file:
 */

typedef struct PieMenu
{
    Tk_Window tkwin;
    Tcl_Interp *interp;
    char *title;
    int titleLength;
    char *preview;
    PieMenuEntry **entries;
    int numEntries;
    int active;
    Tk_Uid group;
    int root_x, root_y;
    int dx, dy;

    /*
     * Information used when displaying widget:
     */

    Tk_3DBorder border;
    int borderWidth;
    Tk_3DBorder activeBorder;
    int activeBorderWidth;
    XFontStruct *fontPtr;
    XFontStruct *titlefontPtr;
    XColor *fg;
    GC textGC;
    XColor *activeFg;
    GC activeGC;

    /*
     * Information used to layout pie menu:
     */

    int width, height;          /* size of the pie menu */
    int title_x, title_y;       /* position of menu title */
    int title_width, title_height;  /* size of menu title */
    int initial_angle;          /* pie menu initial angle in radians */
    int inactive_radius;        /* inactive inner radius */
    int min_radius;             /* minimum label radius */
    int fixed_radius;           /* fixed label radius */
    int extra_radius;           /* extra label radius pad */
    int label_radius;           /* Radius of labels from menu center */
    int center_x, center_y;     /* Menu center */
    XSegment *segments;         /* Line segments to draw */

    /*
     * Miscellaneous information:
     */

    Tk_TimerToken popup_timer_token;
    Cursor cursor;
    PieMenuEntry *postedPie;
    int flags;
    int phase;
    int popup_delay;            /* Delay before popup */
    int shaped;                 /* Use SHAPE extension */
} PieMenu;

/*
 * Flag bits for menus:
 *
 * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
 *				has already been queued to redraw
 *				this window.
 * UPDATE_PENDING:		Non-zero means a DoWhenIdle handler
 *				has already been queued to update
 *				this window.
 * RESIZE_PENDING:		Non-zero means a call to ComputeMenuGeometry
 *				has already been scheduled.
 * POPUP_PENDING:		Non-zero means a call to PopupPieMenu has
 *				already been scheduled.
 */

#define REDRAW_PENDING		1
#define UPDATE_PENDING		2
#define RESIZE_PENDING		4
#define POPUP_PENDING		8

/*
 * Configuration specs valid for the menu as a whole:
 */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
     PIE_ACTIVE_BG_COLOR, Tk_Offset(PieMenu, activeBorder),
     TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
     PIE_ACTIVE_BG_MONO, Tk_Offset(PieMenu, activeBorder),
     TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
     PIE_ACTIVE_BORDER_WIDTH, Tk_Offset(PieMenu, activeBorderWidth), 0},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
     PIE_ACTIVE_FG_COLOR, Tk_Offset(PieMenu, activeFg),
     TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
     PIE_ACTIVE_FG_MONO, Tk_Offset(PieMenu, activeFg),
     TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
     PIE_BG_COLOR, Tk_Offset(PieMenu, border), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
     PIE_BG_MONO, Tk_Offset(PieMenu, border), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
     (char *) NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
     (char *) NULL, 0, 0},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
     PIE_BORDER_WIDTH, Tk_Offset(PieMenu, borderWidth), 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
     "circle", Tk_Offset(PieMenu, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
     (char *) NULL, 0, 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
     PIE_FG, Tk_Offset(PieMenu, fg), 0},
    {TK_CONFIG_FONT, "-font", "font", "Font",
     PIE_FONT, Tk_Offset(PieMenu, fontPtr), 0},
    {TK_CONFIG_STRING, "-title", (char *) NULL, (char *) NULL,
     "", Tk_Offset(PieMenu, title), 0},
    {TK_CONFIG_STRING, "-preview", (char *) NULL, (char *) NULL,
     "", Tk_Offset(PieMenu, preview), 0},
    {TK_CONFIG_FONT, "-titlefont", "font", "Font",
     PIE_FONT, Tk_Offset(PieMenu, titlefontPtr), 0},
    {TK_CONFIG_INT, "-initialangle", "initialAngle", "InitialAngle",
     "0", Tk_Offset(PieMenu, initial_angle), 0},
    {TK_CONFIG_INT, "-inactiveradius", "inactiveRadius", "InactiveRadius",
     PIE_INACTIVE_RADIUS, Tk_Offset(PieMenu, inactive_radius), 0},
    {TK_CONFIG_INT, "-minradius", "minRadius", "MinRadius",
     PIE_MIN_RADIUS, Tk_Offset(PieMenu, min_radius), 0},
    {TK_CONFIG_INT, "-extraradius", "extraRadius", "ExtraRadius",
     PIE_EXTRA_RADIUS, Tk_Offset(PieMenu, extra_radius), 0},
    {TK_CONFIG_INT, "-fixedradius", "fixedRadius", "FixedRadius",
     "0", Tk_Offset(PieMenu, fixed_radius), 0},
    {TK_CONFIG_INT, "-active", "active", "Active",
     "-1", Tk_Offset(PieMenu, active), 0},
    {TK_CONFIG_INT, "-popupdelay", "popupDelay", "PopupDelay",
     PIE_POPUP_DELAY, Tk_Offset(PieMenu, popup_delay), 0},
    {TK_CONFIG_INT, "-shaped", "shaped", "Shaped",
     "1", Tk_Offset(PieMenu, shaped), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, 0}
};

/*
 * Forward declarations for procedures defined later in this file:
 */

int Tk_PieMenuCmd(ClientData clientData, Tcl_Interp * interp, int argc, char **argv);
static int ActivatePieMenuEntry _ANSI_ARGS_((PieMenu * menuPtr, int index, int preview));
static void ComputePieMenuGeometry _ANSI_ARGS_((ClientData clientData));
static int ConfigurePieMenu _ANSI_ARGS_((Tcl_Interp * interp, PieMenu * menuPtr, int argc, char **argv, int flags));
static int ConfigurePieMenuEntry _ANSI_ARGS_((Tcl_Interp * interp,
                                              PieMenu * menuPtr, PieMenuEntry * mePtr, int index,
                                              int argc, char **argv, int flags));
static void DestroyPieMenu _ANSI_ARGS_((ClientData clientData));
static void DestroyPieMenuEntry _ANSI_ARGS_((ClientData clientData));
static void DisplayPieMenu _ANSI_ARGS_((ClientData clientData));
static void UpdatePieMenu _ANSI_ARGS_((ClientData clientData));
static void PopupPieMenu _ANSI_ARGS_((ClientData clientData));
static void EventuallyRedrawPieMenu _ANSI_ARGS_((PieMenu * menuPtr, int index));
static int GetPieMenuIndex _ANSI_ARGS_((Tcl_Interp * interp, PieMenu * menuPtr, char *string, int *indexPtr));
static void PieMenuEventProc _ANSI_ARGS_((ClientData clientData, XEvent * eventPtr));
static int PieMenuWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, int argc, char **argv));
static int UnpostSubPieMenu _ANSI_ARGS_((Tcl_Interp * interp, PieMenu * menuPtr));
static void PopupPieMenu _ANSI_ARGS_((ClientData clientData));
static void NowPopupPieMenu _ANSI_ARGS_((PieMenu * menuPtr));
static void NeverPopupPieMenu _ANSI_ARGS_((PieMenu * menuPtr));
static void EventuallyPopupPieMenu _ANSI_ARGS_((PieMenu * menuPtr));
static void DeferPopupPieMenu _ANSI_ARGS_((PieMenu * menuPtr));
static void ShapePieMenu _ANSI_ARGS_((PieMenu * menuPtr));
static void LayoutPieMenu(PieMenu * menu);
static void UpdatePieMenuEntries(PieMenu * menuPtr);
static int CalcPieMenuItem(PieMenu * menu, int x, int y);

/*
 *--------------------------------------------------------------
 *
 * Tk_PieMenuCmd --
 *
 *	This procedure is invoked to process the "piemenu" Tcl
 *	command.  Read the code and write some user documentation for
 *	details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation for "menu", which this was based on.
 *
 *--------------------------------------------------------------
 */

int Tk_PieMenuCmd(clientData, interp, argc, argv)
ClientData clientData;          /* Main window associated with
                                 * interpreter. */
Tcl_Interp *interp;             /* Current interpreter. */
int argc;                       /* Number of arguments. */
char **argv;                    /* Argument strings. */
{
    Tk_Window tkwin = (Tk_Window) clientData;
    Tk_Window new;
    register PieMenu *menuPtr;
    XSetWindowAttributes atts;

    if(argc < 2)
    {
        Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pathName ?options?\"", (char *) NULL);
        return TCL_ERROR;
    }

    /*
     * Create the new window.  Set override-redirect so the window
     * manager won't add a border or argue about placement, and set
     * save-under so that the window can pop up and down without a
     * lot of re-drawing.
     */

    new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
    if(new == NULL)
    {
        return TCL_ERROR;
    }
    atts.override_redirect = True;
    atts.save_under = True;
    Tk_ChangeWindowAttributes(new, CWOverrideRedirect | CWSaveUnder, &atts);

    /*
     * Initialize the data structure for the menu.
     */

    menuPtr = (PieMenu *) ckalloc(sizeof(PieMenu));
    menuPtr->tkwin = new;
    menuPtr->interp = interp;
    menuPtr->title = NULL;
    menuPtr->titleLength = 0;
    menuPtr->preview = NULL;
    menuPtr->entries = NULL;
    menuPtr->numEntries = 0;
    menuPtr->active = -1;
    menuPtr->group = NULL;
    menuPtr->root_x = 0;
    menuPtr->root_y = 0;
    menuPtr->border = NULL;
    menuPtr->activeBorder = NULL;
    menuPtr->fontPtr = NULL;
    menuPtr->titlefontPtr = NULL;
    menuPtr->fg = NULL;
    menuPtr->textGC = None;
    menuPtr->activeFg = NULL;
    menuPtr->activeGC = None;
    menuPtr->width = 0;
    menuPtr->height = 0;
    menuPtr->title_x = 0;
    menuPtr->title_y = 0;
    menuPtr->title_width = 0;
    menuPtr->title_height = 0;
    menuPtr->initial_angle = 0;
    menuPtr->inactive_radius = PIE_INACTIVE_RADIUS_NUM;
    menuPtr->min_radius = PIE_MIN_RADIUS_NUM;
    menuPtr->extra_radius = PIE_EXTRA_RADIUS_NUM;
    menuPtr->fixed_radius = 0;
    menuPtr->label_radius = 0;
    menuPtr->center_x = 0;
    menuPtr->center_y = 0;
    menuPtr->segments = NULL;
    menuPtr->cursor = None;
    menuPtr->postedPie = NULL;
    menuPtr->flags = 0;
    menuPtr->phase = 0;
    menuPtr->shaped = 1;
    menuPtr->popup_delay = PIE_POPUP_DELAY_NUM;

    Tk_SetClass(new, "PieMenu");
    Tk_CreateEventHandler(menuPtr->tkwin,
                          ExposureMask | StructureNotifyMask |
                          ButtonPressMask | ButtonReleaseMask |
                          PointerMotionMask, PieMenuEventProc, (ClientData) menuPtr);
    Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), PieMenuWidgetCmd, (ClientData) menuPtr, (void (*)()) NULL);
    if(ConfigurePieMenu(interp, menuPtr, argc - 2, argv + 2, 0) != TCL_OK)
    {
        goto error;
    }

    interp->result = Tk_PathName(menuPtr->tkwin);
    return TCL_OK;

  error:
    Tk_DestroyWindow(menuPtr->tkwin);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * PieMenuWidgetCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int PieMenuWidgetCmd(clientData, interp, argc, argv)
ClientData clientData;          /* Information about menu widget. */
Tcl_Interp *interp;             /* Current interpreter. */
int argc;                       /* Number of arguments. */
char **argv;                    /* Argument strings. */
{
    register PieMenu *menuPtr = (PieMenu *) clientData;
    register PieMenuEntry *mePtr;
    int result = TCL_OK;
    int length, type;
    char c;

    if(argc < 2)
    {
        Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option ?arg arg ...?\"", (char *) NULL);
        return TCL_ERROR;
    }
    Tk_Preserve((ClientData) menuPtr);
    c = argv[1][0];
    length = strlen(argv[1]);
    if((c == 'a') && (strncmp(argv[1], "activate", length) == 0) && (length >= 2))
    {
        int index;

        if(argc != 3)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " activate index\"", (char *) NULL);
            goto error;
        }
        if(GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK)
        {
            goto error;
        }
        if(menuPtr->active == index)
        {
            goto done;
        }
        result = ActivatePieMenuEntry(menuPtr, index, 1);
        DeferPopupPieMenu(menuPtr);
    }
    else if((c == 's') && (strncmp(argv[1], "show", length) == 0) && (length >= 2))
    {
        if(argc != 2)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " show\"", (char *) NULL);
            goto error;
        }
        NowPopupPieMenu(menuPtr);
    }
    else if((c == 'p') && (strncmp(argv[1], "pending", length) == 0) && (length >= 2))
    {
        if(argc != 2)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pending\"", (char *) NULL);
            goto error;
        }
        sprintf(interp->result, "%d", (menuPtr->flags & POPUP_PENDING) ? 1 : 0);
    }
    else if((c == 'd') && (strncmp(argv[1], "defer", length) == 0) && (length >= 2))
    {
        if(argc != 2)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " defer\"", (char *) NULL);
            goto error;
        }
        DeferPopupPieMenu(menuPtr);
    }
    else if((c == 'a') && (strncmp(argv[1], "add", length) == 0) && (length >= 2))
    {
        PieMenuEntry **newEntries;

        if(argc < 3)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " add type ?options?\"", (char *) NULL);
            goto error;
        }

        /*
         * Figure out the type of the new entry.
         */

        c = argv[2][0];
        length = strlen(argv[2]);
        if((c == 'c') && (strncmp(argv[2], "command", length) == 0))
        {
            type = COMMAND_ENTRY;
        }
        else if((c == 'p') && (strncmp(argv[2], "piemenu", length) == 0))
        {
            type = PIEMENU_ENTRY;
        }
        else
        {
            Tcl_AppendResult(interp, "bad menu entry type \"",
                             argv[2], "\":  must be command or piemenu", (char *) NULL);
            goto error;
        }

        /*
         * Add a new entry to the end of the menu's array of entries,
         * and process options for it.
         */

        mePtr = (PieMenuEntry *) ckalloc(sizeof(PieMenuEntry));
        newEntries = (PieMenuEntry **) ckalloc((unsigned) ((menuPtr->numEntries + 1) * sizeof(PieMenuEntry *)));
        if(menuPtr->numEntries != 0)
        {
            memcpy((VOID *) newEntries, (VOID *) menuPtr->entries, menuPtr->numEntries * sizeof(PieMenuEntry *));
            ckfree((char *) menuPtr->entries);
        }
        menuPtr->entries = newEntries;
        menuPtr->entries[menuPtr->numEntries] = mePtr;
        menuPtr->numEntries++;
        mePtr->type = type;
        mePtr->piemenuPtr = menuPtr;
        mePtr->label = NULL;
        mePtr->labelLength = 0;
        mePtr->bitmap = None;
        mePtr->width = 0;
        mePtr->height = 0;
        mePtr->x_offset = 0;
        mePtr->y_offset = 0;
        mePtr->label_x = 0;
        mePtr->label_y = 0;
        mePtr->border = NULL;
        mePtr->activeBorder = NULL;
        mePtr->fontPtr = NULL;
        mePtr->textGC = None;
        mePtr->activeGC = None;
        mePtr->slice = 1.0;
        mePtr->angle = 0.0;
        mePtr->dx = 0.0;
        mePtr->dy = 0.0;
        mePtr->subtend = 0.0;
        mePtr->quadrant = 0;
        mePtr->slope = 0.0;
        mePtr->command = NULL;
        mePtr->preview = NULL;
        mePtr->name = NULL;
        mePtr->flags = 0;
        if(ConfigurePieMenuEntry(interp, menuPtr, mePtr, menuPtr->numEntries - 1, argc - 3, argv + 3, 0) != TCL_OK)
        {
            DestroyPieMenuEntry((ClientData) mePtr);
            menuPtr->numEntries--;
            goto error;
        }
        if(!(menuPtr->flags & RESIZE_PENDING))
        {
            menuPtr->flags |= RESIZE_PENDING;
            Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
        }
    }
    else if((c == 'c') && (strncmp(argv[1], "configure", length) == 0))
    {
        if(argc == 2)
        {
            result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs, (char *) menuPtr, (char *) NULL, 0);
        }
        else if(argc == 3)
        {
            result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs, (char *) menuPtr, argv[2], 0);
        }
        else
        {
            result = ConfigurePieMenu(interp, menuPtr, argc - 2, argv + 2, TK_CONFIG_ARGV_ONLY);
        }
    }
    else if((c == 'd') && (strncmp(argv[1], "delete", length) == 0) && (length >= 2))
    {
        int index, i;

        if(argc != 3)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " delete index\"", (char *) NULL);
            goto error;
        }
        if(GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK)
        {
            goto error;
        }
        if(index < 0)
        {
            goto done;
        }
        Tk_EventuallyFree((ClientData) menuPtr->entries[index], DestroyPieMenuEntry);
        for(i = index; i < menuPtr->numEntries - 1; i++)
        {
            menuPtr->entries[i] = menuPtr->entries[i + 1];
        }
        menuPtr->numEntries -= 1;
        if(menuPtr->active == index)
        {
            menuPtr->active = -1;
        }
        else if(menuPtr->active > index)
        {
            menuPtr->active -= 1;
        }
        if(!(menuPtr->flags & RESIZE_PENDING))
        {
            menuPtr->flags |= RESIZE_PENDING;
            Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
        }
    }
    else if((c == 'e') && (length >= 3) && (strncmp(argv[1], "entryconfigure", length) == 0))
    {
        int index;

        if(argc < 3)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"",
                             argv[0], " entryconfigure index ?option value ...?\"", (char *) NULL);
            goto error;
        }
        if(GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK)
        {
            goto error;
        }
        if(index < 0)
        {
            goto done;
        }
        mePtr = menuPtr->entries[index];
        Tk_Preserve((ClientData) mePtr);
        if(argc == 3)
        {
            result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
                                      (char *) mePtr, (char *) NULL, COMMAND_MASK << mePtr->type);
        }
        else if(argc == 4)
        {
            result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
                                      (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
        }
        else
        {
            result = ConfigurePieMenuEntry(interp, menuPtr, mePtr, index,
                                           argc - 3, argv + 3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
        }
        Tk_Release((ClientData) mePtr);
    }
    else if((c == 'i') && (strncmp(argv[1], "index", length) == 0) && (length >= 3))
    {
        int index;

        if(argc != 3)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " index string\"", (char *) NULL);
            goto error;
        }
        if(GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK)
        {
            goto error;
        }
        if(index < 0)
        {
            interp->result = "none";
        }
        else
        {
            sprintf(interp->result, "%d", index);
        }
    }
    else if((c == 'i') && (strncmp(argv[1], "invoke", length) == 0) && (length >= 3))
    {
        int index;

        if(argc != 3)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " invoke index\"", (char *) NULL);
            goto error;
        }
        if(GetPieMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK)
        {
            goto error;
        }
        if(index < 0)
        {
            goto done;
        }
        mePtr = menuPtr->entries[index];
        Tk_Preserve((ClientData) mePtr);
        if(mePtr->command != NULL)
        {
            result = Tcl_GlobalEval(interp, mePtr->command);
        }
        Tk_Release((ClientData) mePtr);
    }
    else if((c == 'p') && (strncmp(argv[1], "post", length) == 0))
    {
        int x, y;

#if 0
        int ix, iy, tmp, err;
#endif
        Tk_Uid group;

        if((argc != 4) && (argc != 5))
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " post x y ?group?\"", (char *) NULL);
            goto error;
        }
        if((Tcl_GetInt(interp, argv[2], &x) != TCL_OK) || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK))
        {
            goto error;
        }
        if(argc == 5)
        {
            group = Tk_GetUid(argv[4]);
        }
        else
        {
            group = Tk_GetUid("default");
        }

        /*
         * Adjust the position of the menu if necessary to keep it
         * on-screen.
         */

        x -= menuPtr->center_x;
        y -= menuPtr->center_y;
#if 0
        ix = x;
        iy = y;

        tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin)) - Tk_Width(menuPtr->tkwin);
        if(x > tmp)
        {
            x = tmp;
        }
        if(x < 0)
        {
            x = 0;
        }
        tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin)) - Tk_Height(menuPtr->tkwin);
        if(y > tmp)
        {
            y = tmp;
        }
        if(y < 0)
        {
            y = 0;
        }

        /*
         * XXX: warp pointer by (x-ix, y-iy) upon popup 
         */
#endif

        Tk_MakeWindowExist(menuPtr->tkwin);
        XRaiseWindow(Tk_Display(menuPtr->tkwin), Tk_WindowId(menuPtr->tkwin));

        Tk_MoveWindow(menuPtr->tkwin, x, y);
        menuPtr->root_x = x + menuPtr->center_x;
        menuPtr->root_y = y + menuPtr->center_y;

        if(Tk_IsMapped(menuPtr->tkwin))
        {
            if(group != menuPtr->group)
            {
                Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
                Tk_ShareEvents(menuPtr->tkwin, group);
            }
        }
        else
        {
            Tk_ShareEvents(menuPtr->tkwin, group);
            EventuallyPopupPieMenu(menuPtr);
            result = ActivatePieMenuEntry(menuPtr, -1, 1);
        }
        menuPtr->group = group;
    }
    else if((c == 'u') && (strncmp(argv[1], "unpost", length) == 0))
    {
        if(argc != 2)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " unpost\"", (char *) NULL);
            goto error;
        }
        NeverPopupPieMenu(menuPtr);
        Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
        Tk_UnmapWindow(menuPtr->tkwin);
        result = ActivatePieMenuEntry(menuPtr, -1, 0);
        if(result == TCL_OK)
        {
            result = UnpostSubPieMenu(interp, menuPtr);
        }
    }
    else if((c == 'g') && (strncmp(argv[1], "grab", length) == 0))
    {
        Tk_Window tkwin;
        int err;

        if((argc != 3) || ((tkwin = Tk_NameToWindow(interp, argv[2], menuPtr->tkwin)) == NULL))
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " grab window\"", (char *) NULL);
            goto error;
        }

        err =
            XGrabPointer(Tk_Display(tkwin),
                         Tk_WindowId(tkwin),
                         False,
                         ButtonPressMask | ButtonReleaseMask |
                         ButtonMotionMask | PointerMotionMask,
                         GrabModeAsync, GrabModeAsync, None, None, TkCurrentTime(((TkWindow *) tkwin)->dispPtr));
        if(err != 0)
        {
            if(err == GrabNotViewable)
            {
                interp->result = "grab failed: window not viewable";
            }
            else if(err == AlreadyGrabbed)
            {
                interp->result = "grab failed: another application has grab";
            }
            else if(err == GrabFrozen)
            {
                interp->result = "grab failed: keyboard or pointer frozen";
            }
            else if(err == GrabInvalidTime)
            {
                interp->result = "grab failed: invalid time";
            }
            else
            {
                char msg[100];

                sprintf(msg, "grab failed for unknown reason (code %d)", err);
                Tcl_AppendResult(interp, msg, (char *) NULL);
            }
            return TCL_ERROR;
        }
    }
    else if((c == 'u') && (strncmp(argv[1], "ungrab", length) == 0))
    {
        Tk_Window tkwin;

        if((argc != 3) || ((tkwin = Tk_NameToWindow(interp, argv[2], menuPtr->tkwin)) == NULL))
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ungrab window\"", (char *) NULL);
            goto error;
        }

        XUngrabPointer(Tk_Display(tkwin), TkCurrentTime(((TkWindow *) tkwin)->dispPtr));

    }
    else if((c == 'd') && (strncmp(argv[1], "distance", length) == 0) && (length >= 3))
    {
        int distance;

        if(argc != 2)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " distance\"", (char *) NULL);
            goto error;
        }
        distance = (int) (sqrt((menuPtr->dx * menuPtr->dx) + (menuPtr->dy * menuPtr->dy)) + 0.499);
        sprintf(interp->result, "%d", distance);
    }
    else if((c == 'd') && (strncmp(argv[1], "direction", length) == 0) && (length >= 3))
    {
        int direction;

        if(argc != 2)
        {
            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " direction\"", (char *) NULL);
            goto error;
        }
        direction = (int) (RAD_TO_DEG(atan2(menuPtr->dy, menuPtr->dx)) + 0.499);
        if(direction < 0)
            direction += 360;
        sprintf(interp->result, "%d", direction);
    }
    else
    {
        Tcl_AppendResult(interp, "bad option \"", argv[1],
                         "\": must be activate, show, add, configure, delete, ",
                         "entryconfigure, index, invoke, post, unpost, pending, ",
                         "defer, grab, or ungrab", (char *) NULL);
        goto error;
    }
  done:
    Tk_Release((ClientData) menuPtr);
    return result;

  error:
    Tk_Release((ClientData) menuPtr);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyPieMenu --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *	to clean up the internal structure of a pie menu at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the pie menu is freed up.
 *
 *----------------------------------------------------------------------
 */

static void DestroyPieMenu(clientData)
ClientData clientData;          /* Info about menu widget. */
{
    register PieMenu *menuPtr = (PieMenu *) clientData;
    int i;

    /*
     * Should we delete the event handler? 
     */

    for(i = 0; i < menuPtr->numEntries; i++)
    {
        DestroyPieMenuEntry((ClientData) menuPtr->entries[i]);
    }
    if(menuPtr->entries != NULL)
    {
        ckfree((char *) menuPtr->entries);
    }
    if(menuPtr->border != NULL)
    {
        Tk_Free3DBorder(menuPtr->border);
    }
    if(menuPtr->activeBorder != NULL)
    {
        Tk_Free3DBorder(menuPtr->activeBorder);
    }
    if(menuPtr->fontPtr != NULL)
    {
        Tk_FreeFontStruct(menuPtr->fontPtr);
    }
    if(menuPtr->fg != NULL)
    {
        Tk_FreeColor(menuPtr->fg);
    }
    if(menuPtr->textGC != None)
    {
        Tk_FreeGC(menuPtr->textGC);
    }
    if(menuPtr->activeFg != NULL)
    {
        Tk_FreeColor(menuPtr->activeFg);
    }
    if(menuPtr->activeGC != None)
    {
        Tk_FreeGC(menuPtr->activeGC);
    }
    if(menuPtr->cursor != None)
    {
        Tk_FreeCursor(menuPtr->cursor);
    }
    ckfree((char *) menuPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyPieMenuEntry --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *	to clean up the internal structure of a pie menu entry at a safe 
 *	time (when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the pie menu entry is freed up.
 *
 *----------------------------------------------------------------------
 */

static void DestroyPieMenuEntry(clientData)
ClientData clientData;          /* Pointer to entry to be freed. */
{
    register PieMenuEntry *mePtr = (PieMenuEntry *) clientData;
    PieMenu *menuPtr = mePtr->piemenuPtr;

    if(menuPtr->postedPie == mePtr)
    {
        if(UnpostSubPieMenu(menuPtr->interp, menuPtr) != TCL_OK)
        {
            TkBindError(menuPtr->interp);
        }
    }
    if(mePtr->label != NULL)
    {
        ckfree(mePtr->label);
    }
    if(mePtr->bitmap != None)
    {
        Tk_FreePixmap(mePtr->bitmap);
    }
    if(mePtr->border != NULL)
    {
        Tk_Free3DBorder(mePtr->border);
    }
    if(mePtr->activeBorder != NULL)
    {
        Tk_Free3DBorder(mePtr->activeBorder);
    }
    if(mePtr->fontPtr != NULL)
    {
        Tk_FreeFontStruct(mePtr->fontPtr);
    }
    if(mePtr->textGC != NULL)
    {
        Tk_FreeGC(mePtr->textGC);
    }
    if(mePtr->activeGC != NULL)
    {
        Tk_FreeGC(mePtr->activeGC);
    }
    if(mePtr->command != NULL)
    {
        ckfree(mePtr->command);
    }
    if(mePtr->name != NULL)
    {
        ckfree(mePtr->name);
    }
    ckfree((char *) mePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigurePieMenu --
 *
 *	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or
 *	reconfigure) a menu widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as colors, font, etc. get set
 *	for menuPtr;  old resources get freed, if there were any.
 *
 *----------------------------------------------------------------------
 */

static int ConfigurePieMenu(interp, menuPtr, argc, argv, flags)
Tcl_Interp *interp;             /* Used for error reporting. */
register PieMenu *menuPtr;      /* Information about widget;  may or may
                                 * not already have values for some fields. */
int argc;                       /* Number of valid entries in argv. */
char **argv;                    /* Arguments. */
int flags;                      /* Flags to pass to Tk_ConfigureWidget. */
{
    XGCValues gcValues;
    GC newGC;
    int i;

    if(Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs, argc, argv, (char *) menuPtr, flags) != TCL_OK)
    {
        return TCL_ERROR;
    }

    /*
     * A few options need special processing, such as setting the
     * background from a 3-D border, or filling in complicated
     * defaults that couldn't be specified to Tk_ConfigureWidget.
     */

    if(menuPtr->title == NULL)
    {
        menuPtr->titleLength = 0;
    }
    else
    {
        menuPtr->titleLength = strlen(menuPtr->title);
    }

    Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);

    gcValues.font = menuPtr->fontPtr->fid;
    gcValues.foreground = menuPtr->fg->pixel;
    gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
    newGC = Tk_GetGC(menuPtr->tkwin, GCForeground | GCBackground | GCFont, &gcValues);
    if(menuPtr->textGC != None)
    {
        Tk_FreeGC(menuPtr->textGC);
    }
    menuPtr->textGC = newGC;

    gcValues.font = menuPtr->fontPtr->fid;
    gcValues.foreground = menuPtr->activeFg->pixel;
    gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
    newGC = Tk_GetGC(menuPtr->tkwin, GCForeground | GCBackground | GCFont, &gcValues);
    if(menuPtr->activeGC != None)
    {
        Tk_FreeGC(menuPtr->activeGC);
    }
    menuPtr->activeGC = newGC;

    /*
     * After reconfiguring a menu, we need to reconfigure all of the
     * entries in the menu, since some of the things in the children
     * (such as graphics contexts) may have to change to reflect changes
     * in the parent.
     */

    for(i = 0; i < menuPtr->numEntries; i++)
    {
        PieMenuEntry *mePtr;

        mePtr = menuPtr->entries[i];
        ConfigurePieMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
                              TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
    }

    if(!(menuPtr->flags & RESIZE_PENDING))
    {
        menuPtr->flags |= RESIZE_PENDING;
        Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigurePieMenuEntry --
 *
 *	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or
 *	reconfigure) one entry in a menu.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information such as label and accelerator get
 *	set for mePtr;  old resources get freed, if there were any.
 *
 *----------------------------------------------------------------------
 */

static int ConfigurePieMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
Tcl_Interp *interp;             /* Used for error reporting. */
PieMenu *menuPtr;               /* Information about whole menu. */
register PieMenuEntry *mePtr;   /* Information about menu entry;  may
                                 * or may not already have values for
                                 * some fields. */
int index;                      /* Index of mePtr within menuPtr's
                                 * entries. */
int argc;                       /* Number of valid entries in argv. */
char **argv;                    /* Arguments. */
int flags;                      /* Additional flags to pass to
                                 * Tk_ConfigureWidget. */
{
    XGCValues gcValues;
    GC newGC, newActiveGC;

    /*
     * If this entry is a piemenu and the piemenu is posted, then unpost
     * it before reconfiguring the entry (otherwise the reconfigure might
     * change the name of the piemenu entry, leaving a posted menu
     * high and dry).
     */

    if(menuPtr->postedPie == mePtr)
    {
        if(UnpostSubPieMenu(menuPtr->interp, menuPtr) != TCL_OK)
        {
            TkBindError(menuPtr->interp);
        }
    }

    if(Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
                          argc, argv, (char *) mePtr, flags | (COMMAND_MASK << mePtr->type)) != TCL_OK)
    {
        return TCL_ERROR;
    }

    /*
     * The code below handles special configuration stuff not taken
     * care of by Tk_ConfigureWidget, such as special processing for
     * defaults, sizing strings, graphics contexts, etc.
     */

    if(mePtr->label == NULL)
    {
        mePtr->labelLength = 0;
    }
    else
    {
        mePtr->labelLength = strlen(mePtr->label);
    }

    if(index != menuPtr->active)
    {
        ActivatePieMenuEntry(menuPtr, index, 0);
    }

    if((mePtr->fontPtr != NULL) || (mePtr->type == PIEMENU_ENTRY))
    {
        gcValues.foreground = menuPtr->fg->pixel;
        gcValues.background = Tk_3DBorderColor((mePtr->border != NULL) ? mePtr->border : menuPtr->border)->pixel;
        if(mePtr->fontPtr != NULL)
        {
            gcValues.font = mePtr->fontPtr->fid;
        }
        else
        {
            if(menuPtr->titlefontPtr != NULL)
                gcValues.font = menuPtr->titlefontPtr->fid;
            else
                gcValues.font = menuPtr->fontPtr->fid;
        }

        /*
         * Note: disable GraphicsExpose events;  we know there won't be
         * obscured areas when copying from an off-screen pixmap to the
         * screen and this gets rid of unnecessary events.
         */

        gcValues.graphics_exposures = False;
        newGC = Tk_GetGC(menuPtr->tkwin, GCForeground | GCBackground | GCFont | GCGraphicsExposures, &gcValues);

        gcValues.foreground = menuPtr->activeFg->pixel;
        gcValues.background = Tk_3DBorderColor((mePtr->activeBorder != NULL) ? mePtr->activeBorder
                                               : menuPtr->activeBorder)->pixel;
        newActiveGC = Tk_GetGC(menuPtr->tkwin, GCForeground | GCBackground | GCFont | GCGraphicsExposures, &gcValues);
    }
    else
    {
        newGC = NULL;
        newActiveGC = NULL;
    }

    if(mePtr->textGC != NULL)
    {
        Tk_FreeGC(mePtr->textGC);
    }
    mePtr->textGC = newGC;

    if(mePtr->activeGC != NULL)
    {
        Tk_FreeGC(mePtr->activeGC);
    }
    mePtr->activeGC = newActiveGC;

    if(!(menuPtr->flags & RESIZE_PENDING))
    {
        menuPtr->flags |= RESIZE_PENDING;
        Tk_DoWhenIdle(ComputePieMenuGeometry, (ClientData) menuPtr);
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * ComputePieMenuGeometry --
 *
 *	This procedure is invoked to recompute the size and
 *	layout of a menu.  It is called as a when-idle handler so
 *	that it only gets done once, even if a group of changes is
 *	made to the menu.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Fields of menu entries are changed to reflect their
 *	current positions, and the size of the menu window
 *	itself may be changed.
 *
 *--------------------------------------------------------------
 */

static void ComputePieMenuGeometry(clientData)
ClientData clientData;          /* Structure describing menu. */
{
    PieMenu *menuPtr = (PieMenu *) clientData;

    if(menuPtr->tkwin == NULL)
    {
        return;
    }

    LayoutPieMenu(menuPtr);

    if((menuPtr->width != Tk_ReqWidth(menuPtr->tkwin)) || (menuPtr->height != Tk_ReqHeight(menuPtr->tkwin)))
    {
        Tk_GeometryRequest(menuPtr->tkwin, menuPtr->width, menuPtr->height);
    }
    else
    {
        /*
         * Must always force a redisplay here if the window is mapped
         * (even if the size didn't change, something else might have
         * changed in the menu, such as a label or accelerator).  The
         * resize will force a redisplay above.
         */

        EventuallyRedrawPieMenu(menuPtr, -1);
    }

    menuPtr->flags &= ~RESIZE_PENDING;
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayPieMenu --
 *
 *	This procedure is invoked to display a pie menu widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the pie menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

static void DisplayPieMenu(clientData)
ClientData clientData;          /* Information about widget. */
{
    register PieMenu *menuPtr = (PieMenu *) clientData;
    register Tk_Window tkwin = menuPtr->tkwin;
    XFontStruct *fontPtr;

    menuPtr->flags &= ~REDRAW_PENDING;
    if((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin))
    {
        return;
    }

    if(menuPtr->titlefontPtr != NULL)
    {
        fontPtr = menuPtr->titlefontPtr;
    }
    else
    {
        fontPtr = menuPtr->fontPtr;
    }

    if(menuPtr->titleLength != 0)
    {
        Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
                           menuPtr->border,
                           menuPtr->borderWidth, menuPtr->borderWidth,
                           Tk_Width(tkwin) - 2 * menuPtr->borderWidth,
                           menuPtr->title_height + 2 * menuPtr->borderWidth, menuPtr->borderWidth, TK_RELIEF_RAISED);

        TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->textGC,
                       fontPtr, menuPtr->title, menuPtr->titleLength,
                       menuPtr->title_x, menuPtr->title_y, TK_NEWLINES_NOT_SPECIAL);
    }

    if(menuPtr->segments)
    {
        XSetLineAttributes(Tk_Display(tkwin), menuPtr->textGC, 0, LineSolid, CapButt, JoinMiter);
        XDrawSegments(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->textGC, menuPtr->segments, menuPtr->numEntries);
    }

    Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin), menuPtr->border,
                       0, 0, Tk_Width(tkwin), Tk_Height(tkwin), menuPtr->borderWidth, TK_RELIEF_RAISED);

    UpdatePieMenuEntries(menuPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * UpdatePieMenu --
 *
 *	This procedure is invoked to update a pie menu widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to update the pie menu in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */

static void UpdatePieMenu(clientData)
ClientData clientData;          /* Information about widget. */
{
    register PieMenu *menuPtr = (PieMenu *) clientData;

    menuPtr->flags &= ~UPDATE_PENDING;
    if((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin))
    {
        return;
    }

    UpdatePieMenuEntries(menuPtr);
}

static void UpdatePieMenuEntries(PieMenu * menuPtr)
{
    register PieMenuEntry *mePtr;
    register Tk_Window tkwin = menuPtr->tkwin;
    XFontStruct *fontPtr;
    int index;
    GC gc;

    for(index = 0; index < menuPtr->numEntries; index++)
    {
        mePtr = menuPtr->entries[index];
        if(!(mePtr->flags & ENTRY_NEEDS_REDISPLAY))
        {
            continue;
        }
        mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;

        /*
         * Background.
         */

        Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin), ((mePtr->activeBorder != NULL)
                                                                   ? mePtr->activeBorder
                                                                   : menuPtr->activeBorder),
                           mePtr->x, mePtr->y,
                           mePtr->width, mePtr->height, menuPtr->activeBorderWidth, ((index == menuPtr->active)
                                                                                     ? TK_RELIEF_SUNKEN
                                                                                     : ((HaveShape && menuPtr->shaped)
                                                                                        ? TK_RELIEF_RAISED
                                                                                        : TK_RELIEF_FLAT)));

        gc = mePtr->textGC;
        if(gc == NULL)
        {
            gc = menuPtr->textGC;
        }

        /*
         * Draw label or bitmap for entry.
         */

        fontPtr = mePtr->fontPtr;
        if(fontPtr == NULL)
        {
            fontPtr = menuPtr->fontPtr;
        }
        if(mePtr->bitmap != None)
        {
            unsigned int width, height;

            Tk_SizeOfPixmap(mePtr->bitmap, &width, &height);
            XCopyArea(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
                      gc, 0, 0, width, height, mePtr->label_x, mePtr->label_y);
        }
        else
        {
            if(mePtr->label != NULL)
            {
                TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
                               fontPtr, mePtr->label, mePtr->labelLength,
                               mePtr->label_x, mePtr->label_y, TK_NEWLINES_NOT_SPECIAL);
            }
        }
    }
}

/*
 *--------------------------------------------------------------
 *
 * GetPieMenuIndex --
 *
 *	Parse a textual index into a pie menu and return the numerical
 *	index of the indicated entry.
 *
 * Results:
 *	A standard Tcl result.  If all went well, then *indexPtr is
 *	filled in with the entry index corresponding to string
 *	(ranges from -1 to the number of entries in the pie menu minus
 *	one).  Otherwise an error message is left in interp->result.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

static int GetPieMenuIndex(interp, menuPtr, string, indexPtr)
Tcl_Interp *interp;             /* For error messages. */
PieMenu *menuPtr;               /* Menu for which the index is being
                                 * specified. */
char *string;                   /* Specification of an entry in menu.  See
                                 * manual entry for valid .*/
int *indexPtr;                  /* Where to store converted relief. */
{
    int i;

    if((string[0] == 'a') && (strcmp(string, "active") == 0))
    {
        *indexPtr = menuPtr->active;
        return TCL_OK;
    }

    if((string[0] == 'l') && (strcmp(string, "last") == 0))
    {
        *indexPtr = menuPtr->numEntries - 1;
        return TCL_OK;
    }

    if((string[0] == 'n') && (strcmp(string, "none") == 0))
    {
        *indexPtr = -1;
        return TCL_OK;
    }

    if(string[0] == '@')
    {
        char xstr[32], ystr[32];
        int x, y;

        if((sscanf(&string[1], "%31[^,],%31[^,]", xstr, ystr) == 2) &&
           (Tcl_GetInt(interp, xstr, &x) == TCL_OK) && (Tcl_GetInt(interp, ystr, &y) == TCL_OK))
        {
            *indexPtr = CalcPieMenuItem(menuPtr, x, y);
            return TCL_OK;
        }
        else
        {
            Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
        }
    }

    if(isdigit(string[0]))
    {
        if(Tcl_GetInt(interp, string, &i) == TCL_OK)
        {
            if((i < menuPtr->numEntries) && (i >= 0))
            {
                *indexPtr = i;
                return TCL_OK;
            }
        }
        else
        {
            Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
        }
    }

    for(i = 0; i < menuPtr->numEntries; i++)
    {
        char *label;

        label = menuPtr->entries[i]->label;
        if((label != NULL) && (Tcl_StringMatch(menuPtr->entries[i]->label, string)))
        {
            *indexPtr = i;
            return TCL_OK;
        }
    }

    Tcl_AppendResult(interp, "bad menu entry index \"", string, "\"", (char *) NULL);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * PieMenuEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on pie menus.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */

static void PieMenuEventProc(clientData, eventPtr)
ClientData clientData;          /* Information about window. */
XEvent *eventPtr;               /* Information about event. */
{
    PieMenu *menuPtr = (PieMenu *) clientData;

    switch (eventPtr->type)
    {
        case Expose:
            if(eventPtr->xexpose.count == 0)
            {
                EventuallyRedrawPieMenu(menuPtr, -1);
            }
            break;
        case DestroyNotify:
            Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));

            /*
             * Careful!  Must delete the event-sharing information here
             * rather than in DestroyPieMenu.  By the time that procedure
             * is called the tkwin may have been reused, resulting in some
             * other window accidentally being cut off from shared events.
             */

            Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
            menuPtr->tkwin = NULL;
            if(menuPtr->flags & REDRAW_PENDING)
            {
                Tk_CancelIdleCall(DisplayPieMenu, (ClientData) menuPtr);
            }
            if(menuPtr->flags & UPDATE_PENDING)
            {
                Tk_CancelIdleCall(UpdatePieMenu, (ClientData) menuPtr);
            }
            if(menuPtr->flags & RESIZE_PENDING)
            {
                Tk_CancelIdleCall(ComputePieMenuGeometry, (ClientData) menuPtr);
            }
            if(menuPtr->flags & POPUP_PENDING)
            {
                Tk_CancelIdleCall(PopupPieMenu, (ClientData) menuPtr);
            }
            Tk_EventuallyFree((ClientData) menuPtr, DestroyPieMenu);
            break;
        case MotionNotify:
            break;
        case ButtonPress:
            break;
        case ButtonRelease:
            break;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedrawPieMenu --
 *
 *	Arrange for an entry of a pie menu, or the whole pie menu, 
 *	to be redisplayed at some point in the future.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A when-idle hander is scheduled to do the redisplay, if there
 *	isn't one already scheduled.
 *
 *----------------------------------------------------------------------
 */

static void EventuallyRedrawPieMenu(menuPtr, index)
register PieMenu *menuPtr;      /* Information about menu to redraw. */
int index;                      /* Which entry to redraw.  If -1, then
                                 * all the entries in the menu are redrawn. */
{
    if(menuPtr->tkwin == NULL)
    {
        return;
    }
    if(index != -1)
    {
        menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
    }
    else
    {
        for(index = 0; index < menuPtr->numEntries; index++)
        {
            menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
        }
        index = -1;
    }
    if((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin) || (menuPtr->flags & REDRAW_PENDING))
    {
        return;
    }

    if(index == -1)
    {
        if(menuPtr->flags & UPDATE_PENDING)
        {
            Tk_CancelIdleCall(UpdatePieMenu, (ClientData) menuPtr);
        }
        Tk_DoWhenIdle(DisplayPieMenu, (ClientData) menuPtr);
        menuPtr->flags |= REDRAW_PENDING;
    }
    else
    {
        Tk_DoWhenIdle(UpdatePieMenu, (ClientData) menuPtr);
        menuPtr->flags |= UPDATE_PENDING;
    }
}

static void PopupPieMenu(clientData)
ClientData clientData;          /* Information about widget. */
{
    register PieMenu *menuPtr = (PieMenu *) clientData;

    NeverPopupPieMenu(menuPtr);

    if(Tk_IsMapped(menuPtr->tkwin))
    {
        return;
    }

    ShapePieMenu(menuPtr);
    Tk_MapWindow(menuPtr->tkwin);
}

static void NowPopupPieMenu(menuPtr)
register PieMenu *menuPtr;
{
    PopupPieMenu((ClientData) menuPtr);
}

static void NeverPopupPieMenu(menuPtr)
register PieMenu *menuPtr;
{
    if(menuPtr->flags & POPUP_PENDING)
    {
        Tk_DeleteTimerHandler(menuPtr->popup_timer_token);
        menuPtr->popup_timer_token = 0;
        menuPtr->flags &= ~POPUP_PENDING;
    }
}

static void EventuallyPopupPieMenu(menuPtr)
register PieMenu *menuPtr;
{
    NeverPopupPieMenu(menuPtr);

    if(Tk_IsMapped(menuPtr->tkwin))
    {
        return;
    }

    menuPtr->popup_timer_token = Tk_CreateTimerHandler(menuPtr->popup_delay, PopupPieMenu, (ClientData) menuPtr);
    menuPtr->flags |= POPUP_PENDING;
}

static void DeferPopupPieMenu(menuPtr)
register PieMenu *menuPtr;
{
    if(menuPtr->flags & POPUP_PENDING)
    {
        EventuallyPopupPieMenu(menuPtr);
    }
}

/*
 *--------------------------------------------------------------
 *
 * UnpostSubPieMenu --
 *
 *	This procedure unposts any submenu.
 *
 * Results:
 *	A standard Tcl return result.  Errors may occur in the
 *	Tcl commands generated to unpost submenus.
 *
 * Side effects:
 *	If there is already a submenu posted, it is unposted.
 *
 *--------------------------------------------------------------
 */

static int UnpostSubPieMenu(interp, menuPtr)
Tcl_Interp *interp;             /* Used for invoking sub-commands and
                                 * reporting errors. */
register PieMenu *menuPtr;      /* Information about menu as a whole. */
{
    int result;

    if(menuPtr->postedPie == NULL)
    {
        return TCL_OK;
    }

    result = Tcl_VarEval(interp, menuPtr->postedPie->name, " unpost", (char *) NULL);
    menuPtr->postedPie = NULL;

    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ActivatePieMenuEntry --
 *
 *	This procedure is invoked to make a particular pie menu 
 *	entry the active one, deactivating any other entry that 
 *	might currently be active.
 *
 * Results:
 *	The return value is a standard Tcl result (errors can occur
 *	while posting and unposting submenus).
 *
 * Side effects:
 *	Pie menu entries get redisplayed, and the active entry 
 *	changes.  Submenus may get posted and unposted.
 *
 *----------------------------------------------------------------------
 */

static int ActivatePieMenuEntry(menuPtr, index, preview)
register PieMenu *menuPtr;      /* Menu in which to activate. */
int index;                      /* Index of entry to activate, or
                                 * -1 to deactivate all entries. */
int preview;                    /* 1 to execute previewer */
{
    register PieMenuEntry *mePtr;
    int result = TCL_OK;

    if(menuPtr->active >= 0)
    {
        mePtr = menuPtr->entries[menuPtr->active];

        EventuallyRedrawPieMenu(menuPtr, menuPtr->active);
    }
    menuPtr->active = index;
    if(index >= 0)
    {
        mePtr = menuPtr->entries[index];
        EventuallyRedrawPieMenu(menuPtr, index);
        if(preview)
        {
            Tk_Preserve((ClientData) mePtr);
            if(mePtr->preview != NULL)
            {
                result = Tcl_GlobalEval(menuPtr->interp, mePtr->preview);
            }
            Tk_Release((ClientData) mePtr);
        }
    }
    else
    {
/* We're doing this in tcl these days, for finer control. */
#if 0
        if(preview && menuPtr->preview)
        {
            result = Tcl_GlobalEval(menuPtr->interp, menuPtr->preview);
        }
#endif
    }
    return result;
}

/*
 * This pie menu tracking code determines the slice the cursor 
 * is in by representing slice edge angles as (quadrant, slope) 
 * pairs that can be quickly computed and compared. 
 *
 * The slope is defined such that it is greater than or equal to zero,
 * less than infinity, and increasing counter-clockwise around the menu. 
 * Each of the four quadrants encompasses one range of slope.
 *
 *                 Y
 *               ^
 *               |     x>0, y>=0
 *  x<=0, y>0 <--+       y/x
 *    -x/y       |        ^
 *        quad 1 | quad 0 |     X
 * -----+--------+--------+----> 
 *      | quad 2 | quad 3
 *      V        |      -x/y
 *   x<0, y<=0   +--> x>=0, y<0
 *     y/x       |
 *               |
 * 
 * The quadrants and slopes of the item edges are all precalculated,
 * during menu layout.
 * The quadrant and slope of the cursor must be calculated frequently
 * during menu tracking, so we just calculate the numerator and
 * denominator of the slope, and avoid an unnecessary division.
 * Instead of calculating "slope = numerator / denominator" then
 * testing "slope < it->slope", every time the cursor moves, we can
 * just test "numerator < (denominator * it->slope)".
 *
 * This algorithm works in a right-side-up coordinate space, but the final
 * results are tranformed into X-windows's up-side-down coordinate system 
 * by subtracting the y values from the window height. 
 */

#define CALC_QUADRANT_SLOPE(x, y, quadrant, numerator, denominator) \
    if ((y) > 0) (quadrant) = ((x) > 0 ? 0 : 1); \
    else if ((y) < 0) (quadrant) = ((x) < 0 ? 2 : 3); \
    else (quadrant) = ((x) > 0 ? 0 : 2); \
    if ((quadrant) & 1) { \
	(numerator) = ABS((x)); (denominator) = ABS((y)); \
    } else { \
	(numerator) = ABS((y)); (denominator) = ABS((x)); \
    }

static int CalcPieMenuItem(PieMenu * menu, int x, int y)
{
    register PieMenuEntry *it;
    int i, order = 0, quadrant;
    int numerator, denominator;
    int first, last_i, last_order;

    /*
     * Translate x and y from root window coordinates so they are 
     * relative to the menu center, in right side up coordinates.
     */

    menu->dx = x = (x - menu->root_x) + 1;
    menu->dy = y = (menu->root_y - y) - 1;

    /*
     * If there are no menu items,
     * or we are within the inactive region in the menu center,
     * then there is no item selected.
     */
    if((menu->numEntries == 0) || ((x * x) + (y * y) < (menu->inactive_radius * menu->inactive_radius)))
    {
        return (-1);
    }

    /*
     * If there's only one item, then that must be it. 
     */
    if(menu->numEntries == 1)
    {
        return (0);
    }

    /*
     * Calculate the quadrant, slope numerator, and slope denominator of
     * the cursor slope, to be used for comparisons.
     */
    CALC_QUADRANT_SLOPE(x, y, quadrant, numerator, denominator);

    /*
     * In most cases, during cursor tracking, the menu item that the
     * cursor is over will be the same as it was before (almost all
     * of the time), or one of the neighboring items (most of the
     * rest of the time). So we check those items first. But to keep
     * things simple, instead of actually checking the items in order of
     * frequency (the current, the two neighbors, then the rest), we just
     * start our loop around the menu items at the item *before* the
     * last selected menu item, so we still check the three most common
     * cases first (neighbor, current, neighbor, rest), without having 
     * to complicate the code with special cases. Another strategy, that
     * might be good for menus with ridiculously many items, would be
     * [to check the current item first, then the two neighbors, then]
     * to do a binary search of the menu items (since they are ordered).
     * But that's more complicated and you shouldn't have that many menu
     * items anyway.
     */

    /*
     * Start at the item before current one.
     */
    first = menu->active - 1;
    if(first < 0)
        first = menu->numEntries - 1;

    /*
     * Initialize last_order such that we will go through the loop
     * at least once, validating last_i and last_order for
     * the next time through the loop.
     */
    last_i = last_order = -1;
    i = first;

    it = menu->entries[i];

    while(1)
    {

/* Legend: c = cursor, e = edge
   <cursor quad>,<edge quad>
         quad 1 | quad 0
         -------+-------
         quad 2 | quad 3
*/

        /*
         * Set order = 1, if shortest direction from edge to cursor is ccw 
         */
        switch ((quadrant - it->quadrant) & 3)
        {

            case 0:            /*
                                 * 0,0   1,1     2,2     3,3
                                 * |ce  ce|   |   | 
                                 * --+--    --+--   --+--   --+--   
                                 * |      | ce|   |ce
                                 */
                /*
                 * slope >= it->slope 
                 */
                order = ((float) numerator >= (float) (denominator * it->slope));
                break;

            case 1:            /*
                                 * 1,0   2,1     3,2     0,3
                                 * c|e   e|   |   |c
                                 * --+--    --+--   --+--   --+--   
                                 * |     c|  e|c      |e
                                 */
                order = 1;
                break;

            case 2:            /*
                                 * 2,0   3,1     0,2     1,3
                                 * |e    e|   |c     c|
                                 * --+--    --+--   --+--   --+--   
                                 * c|     |c     e|   |e
                                 */
                /*
                 * slope < it->slope 
                 */
                order = ((float) numerator < (float) (denominator * it->slope));
                break;

            case 3:            /*
                                 * 3,0   0,1     1,2     2,3
                                 * |e    e|c     c|   |
                                 * --+--    --+--   --+--   --+--   
                                 * |c     |  e|  c|e
                                 */
                order = 0;
                break;
        }

        /*
         * If we were counter-clockwise of the last leading edge,
         * and we're clockwise of this leading edge,
         * then we were in the last menu item.
         * (Note: first time through this loop last_order = -1 so we'll
         * go back through the loop at least once, after validating
         * last_order and last_i.)
         */
        if((last_order == 1) && (order == 0))
        {
            return (last_i);
        }
        last_order = order;

        /*
         * Remember this menu item index, and move on to the next one
         * counter-clockwise around the circle.
         */
        last_i = i;
        if(++i >= menu->numEntries)
        {
            i = 0;
        }
        it = menu->entries[i];

        /*
         * If we've checked all the others, then that must have been it. 
         * This saves us from checking the leading edge of the first
         * item again (It's also insurance against layout bugs.)
         */
        if(i == first)
        {
            return (last_i);
        }
    }
}

static void LayoutPieMenu(PieMenu * menu)
{
    int i;
    int total_slice, radius;
    int minx, miny, maxx, maxy;
    float angle;
    PieMenuEntry *it, *last;
    XFontStruct *font, *titlefont;

    /*
     * Calculate the sum of the menu item slice sizes.
     * Each menu item will get a (slice / total_slice) sized slice of the pie.
     */
    total_slice = 0;
    for(i = 0; i < menu->numEntries; i++)
    {
        total_slice += menu->entries[i]->slice;
    }

    if((titlefont = menu->titlefontPtr) == NULL)
        titlefont = menu->fontPtr;

    /*
     * Calculate the subtend, angle, cosine, sine, quadrant, slope,
     * and size of each menu item.
     */
    angle = DEG_TO_RAD(menu->initial_angle);
    for(i = 0; i < menu->numEntries; i++)
    {
        register float edge_dx, edge_dy, numerator, denominator, twist;
        register int quadrant;

        it = menu->entries[i];
        if((font = it->fontPtr) == NULL)
            font = menu->fontPtr;

        if(it->bitmap != None)
        {
            unsigned int bitmapWidth, bitmapHeight;

            Tk_SizeOfPixmap(it->bitmap, &bitmapWidth, &bitmapHeight);
            it->height = bitmapHeight;
            it->width = bitmapWidth;
        }
        else
        {
            it->height = font->ascent + font->descent;
            if(it->label != NULL)
            {
                (void) TkMeasureChars(font, it->label,
                                      it->labelLength, 0, (int) 100000, TK_NEWLINES_NOT_SPECIAL, &it->width);
            }
            else
            {
                it->width = 0;
            }
        }
        it->height += 2 * menu->activeBorderWidth + 2;
        it->width += 2 * menu->activeBorderWidth + 2;

        it->subtend = TWO_PI * it->slice / total_slice;
        twist = it->subtend / 2.0;
        if(i != 0)
            angle += twist;
        it->angle = angle;
        it->dx = cos(angle);
        it->dy = sin(angle);
        edge_dx = cos(angle - twist);
        edge_dy = sin(angle - twist);
        CALC_QUADRANT_SLOPE(edge_dx, edge_dy, quadrant, numerator, denominator);
        it->quadrant = quadrant;
        it->slope = (float) numerator / (float) denominator;
        angle += twist;
    }

    if((radius = menu->fixed_radius) == 0)
    {
        radius = menu->min_radius;
        if(menu->numEntries > 1)
        {
            last = menu->entries[menu->numEntries - 1];
            for(i = 0; i < menu->numEntries; i++)
            {
                float dx, dy, ldx, ldy;
                int width, height, lwidth, lheight;

                it = menu->entries[i];

                dx = it->dx;
                dy = it->dy;
                width = it->width;
                height = it->height;
                ldx = last->dx;
                ldy = last->dy;
                lwidth = last->width;
                lheight = last->height;
                while(1)
                {
                    register int x, y, lx, ly, x0max, y0max, x1min, y1min;

                    x = dx * radius + it->x_offset;
                    y = dy * radius + it->y_offset;
                    lx = ldx * radius + last->x_offset;
                    ly = ldy * radius + last->y_offset;

                    /*
                     * Translate x y with respect to label size and position 
                     */
                    if(ABS(x) <= 2)
                    {
                        x -= width / 2;
                        if(y < 0)
                            y -= height;
                    }
                    else
                    {
                        if(x < 0)
                            x -= width;
                        y -= height / 2;
                    }

                    if(ABS(lx) <= 2)
                    {
                        lx -= lwidth / 2;
                        if(ly < 0)
                            ly -= lheight;
                    }
                    else
                    {
                        if(lx < 0)
                            lx -= lwidth;
                        ly -= lheight / 2;
                    }

                    /*
                     * Do rects (x y width height) and (lx ly lwidth lheight) overlap? 
                     */
                    x0max = x > lx ? x : lx;
                    y0max = y > ly ? y : ly;
                    x1min = x + width < lx + lwidth ? x + width : lx + lwidth;
                    y1min = y + height < ly + lheight ? y + height : ly + lheight;
                    if(!((x0max < x1min) && (y0max < y1min)))
                    {           /* If they don't overlap */
                        /*
                         * They are far enough out, so move on. 
                         */
                        break;
                    }
                    /*
                     * Push the menu radius out a step and try again 
                     */
                    radius++;
                }
                /*
                 * Loop on to next menu item 
                 */
                last = it;
            }
        }
        radius += menu->extra_radius;
    }
    menu->label_radius = radius;

    /*
     * Finally position all the menu labels at the same radius.
     * Figure out the bounding box of the labels. 
     */
    minx = miny = maxx = maxy = 0;
    for(i = 0; i < menu->numEntries; i++)
    {
        it = menu->entries[i];

        it->x = radius * it->dx + it->x_offset;
        it->y = radius * it->dy + it->y_offset;

        /*
         * Translate x y with respect to label size and position 
         */
        if(ABS(it->x) <= 2)
        {
            it->x -= it->width / 2;
            if(it->y < 0)
                it->y -= it->height;
        }
        else
        {
            if(it->x < 0)
                it->x -= it->width;
            it->y -= it->height / 2;
        }

        it->label_x = it->x + menu->activeBorderWidth + 1;
        it->label_y = it->y - menu->activeBorderWidth - 1;
        if(it->bitmap == None)
        {
            it->label_y -= (it->fontPtr ? it->fontPtr : menu->fontPtr)->ascent;
        }

        if(it->x < minx)
            minx = it->x;
        if((it->x + it->width) > maxx)
            maxx = (it->x + it->width);
        if(it->y < miny)
            miny = it->y;
        if((it->y + it->height) > maxy)
            maxy = (it->y + it->height);
    }

    if(menu->titleLength != 0)
    {
        menu->title_height = titlefont->ascent + titlefont->descent + 2;
        (void) TkMeasureChars(titlefont, menu->title,
                              menu->titleLength, 0, (int) 100000, TK_NEWLINES_NOT_SPECIAL, &menu->title_width);
        menu->title_width += 2;
        if(-(menu->title_width / 2) < minx)
            minx = -(menu->title_width / 2);
        if((menu->title_width / 2) > maxx)
            maxx = (menu->title_width / 2);
        maxy += (2 * menu->borderWidth) + menu->title_height;
    }
    else
    {
        menu->title_width = menu->title_height = 0;
    }

    minx -= 2 * menu->borderWidth;
    miny -= 2 * menu->borderWidth;
    maxx += 2 * menu->borderWidth;
    maxy += 2 * menu->borderWidth;

    menu->center_x = -minx;
    menu->center_y = maxy;      /* y flip */
    menu->width = maxx - minx;
    menu->height = maxy - miny;

/*  menu->title_x = (menu->width - menu->title_width) / 2 + 1; */
    menu->title_x = menu->center_x - menu->title_width / 2 + 1;
    menu->title_y = 2 * menu->borderWidth + titlefont->ascent + 1;

    /*
     * Translate the menu items to the center of the menu, in X coordinates. 
     */
    for(i = 0; i < menu->numEntries; i++)
    {
        it = menu->entries[i];
        it->x = menu->center_x + it->x;
        it->y = (menu->center_y - it->y) - it->height;  /* y flip */
        it->label_x = menu->center_x + it->label_x;
        it->label_y = (menu->center_y - it->label_y) - it->height;  /* y flip */
    }

    if(menu->segments != NULL)
    {
        ckfree((char *) menu->segments);
    }
    menu->segments = (XSegment *) ckalloc(menu->numEntries * sizeof(XSegment));

    if(menu->numEntries > 1)
    {
        XSegment *seg = menu->segments;

        angle = DEG_TO_RAD(menu->initial_angle) - (menu->entries[0]->subtend / 2.0);
        for(i = 0; i < menu->numEntries; i++)
        {
            it = menu->entries[i];
            seg->x1 = menu->center_x + (cos(angle) * menu->inactive_radius);
            seg->y1 = menu->center_y - (sin(angle) * menu->inactive_radius);
            seg->x2 = menu->center_x + (cos(angle) * (menu->label_radius - PIE_SPOKE_INSET));
            seg->y2 = menu->center_y - (sin(angle) * (menu->label_radius - PIE_SPOKE_INSET));
            seg++;
            angle += it->subtend;
        }
    }
}

static void ShapePieMenu(menuPtr)
PieMenu *menuPtr;
{
    Display *dpy;
    Window win, shape;
    GC gc;
    XGCValues values;
    PieMenuEntry *it;
    int i;

    if(HaveShape == 0)
        return;

    if(menuPtr->shaped == 0)
    {
        return;
    }

    dpy = Tk_Display(menuPtr->tkwin);

    if(HaveShape == -1)
    {
        int t1, t2;

        if(XShapeQueryExtension(dpy, &t1, &t2))
        {
            HaveShape = 1;
        }
        else
        {
            HaveShape = 0;
            return;
        }
    }

    Tk_MakeWindowExist(menuPtr->tkwin);
    win = Tk_WindowId(menuPtr->tkwin);

    shape = XCreatePixmap(dpy, RootWindowOfScreen(Tk_Screen(menuPtr->tkwin)), menuPtr->width, menuPtr->height, 1);
    gc = XCreateGC(dpy, shape, 0, &values);

    XSetForeground(dpy, gc, 0);
    XFillRectangle(dpy, shape, gc, 0, 0, menuPtr->width, menuPtr->height);

    XSetForeground(dpy, gc, 1);
    if(menuPtr->titleLength != 0)
    {
        int bw = menuPtr->borderWidth;

        XFillRectangle(dpy, shape, gc, bw, bw, menuPtr->width - bw * 2, menuPtr->title_height + bw * 2);
    }

    for(i = 0; i < menuPtr->numEntries; i++)
    {
        it = menuPtr->entries[i];
        XFillRectangle(dpy, shape, gc, it->x, it->y, it->width, it->height);
    }

    XFreeGC(dpy, gc);
    XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet);
}
